#include <linux/types.h>
#include <asm/memory.h>

#define DEFAULT_CLOCK   (24000000)  // clock frequency: 24M 
#define BAUDRATE        (115200)    

#define FR_BUSY    (1u << 3)
#define FR_RXFE    (1u << 4)
#define CR_RXEN    (1u << 9)
#define CR_TXEN    (1u << 8)
#define CR_UARTEN  (1u << 0)
#define IMSC_RTIM  (1u << 6)
#define IMSC_TXIM  (1u << 5)
#define IMSC_RXIM  (1u << 4)
#define LCR_FEN    (1u << 4)
#define LCR_STP2   (1u << 3)
#define ICR_RTIC   (1u << 6)
#define ICR_TXIC   (1u << 5)
#define ICR_RXIC   (1u << 4)

struct pl011_regs {
    uint32_t DR;       /* 0x000 */
    uint32_t RSR;      /* 0x004 */
    uint32_t res1[4];  /* 0x008-0x014 */
    uint32_t FR;       /* 0x018 */
    uint32_t res2;     /* 0x01C */
    uint32_t ILPR;     /* 0x020 */
    uint32_t IBRD;     /* 0x024 */    
    uint32_t FBRD;     /* 0x028 */
    uint32_t LCR_H;    /* 0x02C */
    uint32_t CR;       /* 0x030 */
    uint32_t IFLS;     /* 0x034 */
    uint32_t IMSC;     /* 0x038 */
    uint32_t RIS;      /* 0x03C */
    uint32_t MIS;      /* 0x040 */
    uint32_t ICR;      /* 0x044 */
    uint32_t DMACR;    /* 0x048 */
};

struct pl011 {
    // uint64_t base_address;
    uint64_t base_clock;
    uint32_t baudrate;
    uint32_t data_bits;
    uint32_t stop_bits;
    volatile struct pl011_regs *regs; 
};

struct pl011 uart_ns;
char uart_init_flag = 0; // 标志uart是否初始化

static void calculate_set_divisiors(const struct pl011 *dev) {
    // 64 * F_UARTCLK / (16 * B) = 4 * F_UARTCLK / B
    const uint32_t div = 4 * dev->base_clock / dev->baudrate;
    uint32_t fractional, integer;

    fractional = div & 0x3f;
    integer = (div >> 6) & 0xffff;

    dev->regs->FBRD = fractional;
    dev->regs->IBRD = integer;
}

void wait_tx_complete(const volatile struct pl011_regs *regs) {
    while ((regs->FR & FR_BUSY) != 0) {}
}

int pl011_putc(struct pl011 *dev, uint8_t c) {
    dev->regs->DR = c;
    wait_tx_complete(dev->regs);
    return 0;
}

int pl011_init(struct pl011 *dev, uint64_t base_address) {
    uint32_t lcr;

    dev->base_clock = DEFAULT_CLOCK;
    dev->baudrate = BAUDRATE;
    dev->data_bits = 8;
    dev->stop_bits = 1;
    dev->regs = (struct pl011_regs *)base_address;

    // Set frequency divisors (UARTIBRD and UARTFBRD) to configure the speed
    calculate_set_divisiors(dev);

    // Disable UART before anything else
    dev->regs->CR &= ~(CR_UARTEN);
    wait_tx_complete(dev->regs);

    /* Configure FIFO */
    lcr = 0x0;
    // WLEN part of UARTLCR_H, you can check that this calculation does the
    // right thing for yourself
    lcr |= ((dev->data_bits - 1) & 0x3) << 5;
    // Configure the number of stop bits
    if (dev->stop_bits == 2)
        lcr |= LCR_STP2;

    // Enable FIFO
    lcr |= LCR_FEN;
    dev->regs->LCR_H = lcr;

    // Disalbe all interrupts by setting corresponding bits to 0
    dev->regs->IMSC = 0x0;
    // Disable DMA by setting all bits to 0
    dev->regs->DMACR = 0x0;

    // Enable uart TX and RX
    dev->regs->CR |= CR_TXEN | CR_RXEN;
    // Finally enable UART
    dev->regs->CR |= CR_UARTEN;
    return 0;
}

void uart_putchar(char c) {
	if (uart_init_flag == 0)
		return;
	pl011_putc(&uart_ns, c);
}

#define UART_PA		0x9000000
#define UART_SIZE	0x1000
#define UART_VA		MODULES_VADDR

void uart_init(void) {
	pl011_init(&uart_ns, UART_VA);
	uart_init_flag = 1;
}
